/*
 * jxSwing Project (2014)
 * 
 * by Denilson Edinaldo Pinto
 * 
 * LGPL
 * Copyright (C) 2014
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.jxSwing.component.table;

import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.image.BufferedImage;
import java.awt.print.PrinterException;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import javax.print.PrintService;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.swing.JTable;
import javax.swing.JViewport;
import javax.swing.ListSelectionModel;
import javax.swing.RowFilter;
import javax.swing.RowSorter.SortKey;
import javax.swing.SortOrder;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.LineBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.RowSorterEvent;
import javax.swing.event.TableModelEvent;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableRowSorter;

import org.jxSwing.Cleanable;
import org.jxSwing.component.JXComponent;
import org.jxSwing.component.table.cellEditor.BeanEditor;
import org.jxSwing.component.table.cellRenderer.BeanRenderer;
import org.jxSwing.i18n.I18N;
import org.jxUtils.check.CheckUtil;
import org.jxUtils.check.ParameterCheck;
import org.jxUtils.collection.CollectionUtil;
import org.jxUtils.collection.ListSet;
import org.jxUtils.enumeration.CSVSeparator;
import org.jxUtils.file.JXFile;
import org.jxUtils.system.SystemProperties;

import sun.awt.image.ImageFormatException;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

/**
 * @author Denilson Edinaldo Pinto
 * @porject jxSwing
 * @package org.jxSwing.component.table
 * @date 22/01/2014 - 20:02:11
 * @param <Bean>
 */
public class BeanTable<Bean> extends JTable implements JXComponent, Cleanable {
	
	private static final long serialVersionUID = 8810731473285486010L;
	
	private DefaultTableColumnModel columnModel = null;
	
	private Class<?> beanClass = null;
	
	private boolean showLineNumber = true;
	
	private boolean atributte = true;
	
	private boolean required = false;
	
	private boolean requiredBorderEnabled = true;
	
	private int index = -1;
	
	private int lastSelectedRow = -1;
	
	/**
	 * @param bean
	 */
	public BeanTable(final Bean bean) {
	
		this(new BeanTableModel<Bean>(bean));
	}
	
	/**
	 * @param beanTableColumn
	 * @param values
	 */
	public BeanTable(final BeanTableColumn beanTableColumn, final Collection<?> values) {
	
		this(new BeanTableModel<>(beanTableColumn, values));
	}
	
	/**
	 * @param beanTableColumn
	 * @param values
	 */
	public BeanTable(final BeanTableColumn beanTableColumn, final Object[] values) {
	
		this(new BeanTableModel<>(beanTableColumn, Arrays.asList(values)));
	}
	
	/**
	 * @param model
	 */
	public BeanTable(final BeanTableModel<?> model) {
	
		super(model);
		
		beanClass = getModel().getBeanClass();
		
		initGUI();
	}
	
	/**
	 * @param beanClass
	 */
	public BeanTable(final Class<?> beanClass) {
	
		this(new BeanTableModel<Bean>(beanClass));
	}
	
	/**
	 * @param values
	 */
	public BeanTable(final Collection<?> values) {
	
		this(new BeanTableModel<>(values));
	}
	
	/**
	 * @param values
	 */
	public BeanTable(final Object[] values) {
	
		this(Arrays.asList(values));
	}
	
	/**
	 * @param container
	 * @return {@link Image} or <b>null</b>
	 */
	public static final Image getOwnerWindowIcon(final Container container) {
	
		//
		if(container instanceof Window) {
			
			final Window window = (Window)container;
			
			return window.getIconImages().size() > 0 ? window.getIconImages().get(0) : null;
			
		}else {
			
			return getOwnerWindowIcon(container.getParent());
		}
	}
	
	/**
	 *
	 */
	private void configNewRow() {
	
		justifyColumnsSize();
		
		setRowSorter(new TableRowSorter<BeanTableModel<Bean>>(getModel()));
	}
	
	/**
	 *
	 */
	private void hideColumns() {
	
		//
		for(final BeanTableColumn beanTableColumn : getColumns()) {
			
			// Se a Coluna não estiver marcada como Visível
			if( !beanTableColumn.isVisible()) {
				
				setColumnVisible(beanTableColumn.getName(), false);
			}
		}
	}
	
	/**
	 *
	 */
	private void initGUI() {
	
		BeanTableUtil.applyEditorAndRenderer(this);
		
		hideColumns();
		
		setRowSorter(new TableRowSorter<BeanTableModel<Bean>>(getModel()));
		setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		setAutoResizeMode(AUTO_RESIZE_SUBSEQUENT_COLUMNS);
		setRowHeight(25);
		
		//
		addComponentListener(new ComponentAdapter() {
			
			@Override
			public void componentResized(final ComponentEvent componentEvent) {
			
				final int width = (int)(getParent().getWidth() * 0.7);
				final int height = (int)(getParent().getHeight() * 0.7);
				
				final Dimension dimension = new Dimension(width >= 500 ? width : 500, height >= 350 ? height : 350);
				
				setSizeToDialogInBeanEditor(dimension);
			}
		});
		
		//
		getSelectionModel().addListSelectionListener(new ListSelectionListener() {
			
			@Override
			public void valueChanged(final ListSelectionEvent listSelectionEvent) {
			
				lastSelectedRow = getSelectedRow();
			}
		});
	}
	
	/**
	 * Ajusta a largura preferida da coluna visível especificada pelo columnIndex.<br>
	 * A coluna será larga o bastante para mostrar o cabeçalho da coluna e a célula de maior conteúdo.
	 * 
	 * @param table
	 * @param columnIndex
	 */
	private void packColumnToData(final int columnIndex) {
	
		//
		SwingUtilities.invokeLater(new Runnable() {
			
			@Override
			public void run() {
			
				//
				final TableColumn column = getColumnModel().getColumn(columnIndex);
				
				//
				TableCellRenderer cellRenderer = column.getHeaderRenderer();
				
				//
				if(cellRenderer == null) {
					
					cellRenderer = getTableHeader().getDefaultRenderer();
				}
				
				//
				Component component = cellRenderer.getTableCellRendererComponent(BeanTable.this, column.getHeaderValue(), false, false, 0, 0);
				
				// Obtém a Largura do Cabeçalho da Coluna
				int width = component.getPreferredSize().width;
				
				//
				for(int rowIndex = 0; rowIndex < getRowCount(); rowIndex++) {
					
					cellRenderer = getCellRenderer(rowIndex, columnIndex);
					
					try {
						//
						final Object value = getValueAt(rowIndex, columnIndex);
						
						component = cellRenderer.getTableCellRendererComponent(BeanTable.this, value, false, false, rowIndex, columnIndex);
						
					}catch(final NullPointerException nullPointerException) {
						// ignore
					}
					
					// Obtém a maior Largura
					width = Math.max(width, component.getPreferredSize().width);
				}
				
				// Configura a Largura
				width += 2 * 5;
				
				//
				column.setPreferredWidth(width);
			}
		});
	}
	
	/**
	 *
	 */
	final void preparePrintTable() {
	
		clearSelection();
		
		//
		for(int colIndex = 0; colIndex < getColumnCount(); colIndex++) {
			
			final TableCellRenderer renderer = getColumnModel().getColumn(colIndex).getCellRenderer();
			
			//
			if(renderer instanceof BeanRenderer) {
				
				final BeanRenderer beanRenderer = (BeanRenderer)renderer;
				
				beanRenderer.setVisibleAddButton(false);
			}
		}
	}
	
	/**
	 *
	 */
	final void restorePrintTable() {
	
		//
		for(int colIndex = 0; colIndex < getColumnCount(); colIndex++) {
			
			final TableCellRenderer renderer = getColumnModel().getColumn(colIndex).getCellRenderer();
			
			//
			if(renderer instanceof BeanRenderer) {
				
				((BeanRenderer)renderer).setVisibleAddButton(true);
			}
		}
	}
	
	/**
	 * @param locked
	 */
	final void setLocked(final boolean locked) {
	
		getModel().setLocked(locked);
	}
	
	/**
	 * Inserts a new Bean in the last row of the table
	 * 
	 * @return last row index added or -1
	 */
	public final synchronized int addRow() {
	
		final int lastRowIndexAdded = getModel().addRow();
		
		configNewRow();
		
		scrollToVisible(lastRowIndexAdded, 0);
		
		return lastRowIndexAdded;
	}
	
	/**
	 * Inserts bean in the last row of the table
	 * 
	 * @param bean
	 * @return last row index added
	 */
	public final synchronized int addRow(final Bean bean) {
	
		final int lastRowIndexAdded = getModel().addRow(bean);
		
		configNewRow();
		
		scrollToVisible(lastRowIndexAdded, 0);
		
		return lastRowIndexAdded;
	}
	
	/**
	 * Inserts bean in rowIndex of the table
	 * 
	 * @param rowIndex
	 * @param bean
	 * @return last row index added
	 */
	public final synchronized int addRow(final int rowIndex, final Bean bean) {
	
		final int lastRowIndexAdded = getModel().addRow(rowIndex, bean);
		
		configNewRow();
		
		scrollToVisible(lastRowIndexAdded, 0);
		
		return lastRowIndexAdded;
	}
	
	/**
	 * Inserts beans in rowIndex of the table
	 * 
	 * @param rowIndex
	 * @param bean
	 * @return last row index added
	 */
	public final synchronized int addRow(final int rowIndex, final Collection<Bean> beans) {
	
		final int lastRowIndexAdded = getModel().addRow(rowIndex, beans);
		
		configNewRow();
		
		scrollToVisible(lastRowIndexAdded, 0);
		
		return lastRowIndexAdded;
	}
	
	/**
	 * Inserts a new Bean after rowIndex of the table
	 * 
	 * @return last row index added or -1
	 */
	public final synchronized int addRowAfter(final int rowIndex) {
	
		final int lastRowIndexAdded = getModel().addRowAfter(rowIndex);
		
		configNewRow();
		
		scrollToVisible(lastRowIndexAdded, 0);
		
		return lastRowIndexAdded;
	}
	
	/**
	 * Inserts bean after rowIndex of the table
	 * 
	 * @param rowIndex
	 * @param bean
	 * @return last row index added
	 */
	public final synchronized int addRowAfter(final int rowIndex, final Bean bean) {
	
		final int lastRowIndexAdded = getModel().addRowAfter(rowIndex, bean);
		
		configNewRow();
		
		scrollToVisible(lastRowIndexAdded, 0);
		
		return lastRowIndexAdded;
	}
	
	/**
	 * Inserts a new Bean before rowIndex of the table
	 * 
	 * @return last row index added or -1
	 */
	public final synchronized int addRowBefore(final int rowIndex) {
	
		final int lastRowIndexAdded = getModel().addRowBefore(rowIndex);
		
		configNewRow();
		
		scrollToVisible(lastRowIndexAdded, 0);
		
		return lastRowIndexAdded;
	}
	
	/**
	 * Inserts bean before rowIndex of the table
	 * 
	 * @param rowIndex
	 * @param bean
	 * @return last row index added
	 */
	public final synchronized int addRowBefore(final int rowIndex, final Bean bean) {
	
		final int lastRowIndexAdded = getModel().addRowBefore(rowIndex, bean);
		
		configNewRow();
		
		scrollToVisible(lastRowIndexAdded, 0);
		
		return lastRowIndexAdded;
	}
	
	/**
	 * @param beans
	 * @return last row index added or -1
	 */
	public final synchronized int addRows(final Bean[] beans) {
	
		final int lastRowIndexAdded = getModel().addRows(beans);
		
		configNewRow();
		
		scrollToVisible(lastRowIndexAdded, 0);
		
		return lastRowIndexAdded;
	}
	
	/**
	 * @param beans
	 * @return last row index added or -1
	 */
	public final synchronized int addRows(final Collection<Bean> beans) {
	
		final int lastRowIndexAdded = getModel().addRows(beans);
		
		configNewRow();
		
		scrollToVisible(lastRowIndexAdded, 0);
		
		return lastRowIndexAdded;
	}
	
	/**
	 * Inserts beans after rowIndex of the table
	 * 
	 * @param beans
	 * @return last row index added or -1
	 */
	public final synchronized int addRowsAfter(final int rowIndex, final Bean[] beans) {
	
		final int lastRowIndexAdded = getModel().addRowsAfter(rowIndex, beans);
		
		configNewRow();
		
		scrollToVisible(lastRowIndexAdded, 0);
		
		return lastRowIndexAdded;
	}
	
	/**
	 * Inserts beans after rowIndex of the table
	 * 
	 * @param beans
	 * @return last row index added
	 */
	public final synchronized int addRowsAfter(final int rowIndex, final Collection<Bean> beans) {
	
		final int lastRowIndexAdded = getModel().addRowsAfter(rowIndex, beans);
		
		configNewRow();
		
		scrollToVisible(lastRowIndexAdded, 0);
		
		return lastRowIndexAdded;
	}
	
	/**
	 * Inserts beans before rowIndex of the table
	 * 
	 * @param beans
	 * @return last row index added
	 */
	public final synchronized int addRowsBefore(final int rowIndex, final Bean[] beans) {
	
		final int lastRowIndexAdded = getModel().addRowsBefore(rowIndex, beans);
		
		configNewRow();
		
		scrollToVisible(lastRowIndexAdded, 0);
		
		return lastRowIndexAdded;
	}
	
	/**
	 * Inserts beans before rowIndex of the table
	 * 
	 * @param beans
	 * @return last row index added
	 */
	public final synchronized int addRowsBefore(final int rowIndex, final Collection<Bean> beans) {
	
		final int lastRowIndexAdded = getModel().addRowsBefore(rowIndex, beans);
		
		configNewRow();
		
		scrollToVisible(lastRowIndexAdded, 0);
		
		return lastRowIndexAdded;
	}
	
	/**
	 * @param autoResizeColumns
	 */
	public final synchronized void autoResizeColumns(final boolean autoResizeColumns) {
	
		//
		if(autoResizeColumns) {
			
			setAutoResizeMode(AUTO_RESIZE_SUBSEQUENT_COLUMNS);
			
		}else {
			
			setAutoResizeMode(AUTO_RESIZE_OFF);
		}
	}
	
	@Override
	public final void clear() {
	
		getModel().clear();
		
		configNewRow();
	}
	
	/**
	 *
	 */
	public final void clearFilter() {
	
		filter(null, false);
	}
	
	/**
	 * @param regex
	 * @param caseSensitive
	 * @param columns
	 * @return resultCount
	 */
	public final int filter(final String regex, final boolean caseSensitive, final int... columns) {
	
		//
		if(getRowSorter() != null) {
			
			//
			if(regex != null && !regex.trim().isEmpty()) {
				
				String filter = regex.trim();
				
				filter = filter.replace("(", "\\(").replace(")", "\\)");
				filter = filter.replace("[", "\\[").replace("]", "\\]");
				filter = filter.replace("^", "\\^").replace("$", "\\$");
				filter = filter.replace(".", "\\.");
				
				try {
					//
					getRowSorter().setRowFilter(RowFilter.regexFilter((caseSensitive ? "" : "(?i)") + filter, columns));
					
				}catch(final Throwable throwable) {
					
					throwable.printStackTrace();
				}
				
			}else {
				
				getRowSorter().setRowFilter(null);
			}
		}
		
		return getRowCount();
	}
	
	/**
	 * @param csvFile
	 * @param separator
	 * @throws IOException
	 */
	public final void fromCSV(final File csvFile, final CSVSeparator separator) throws IOException {
	
		BeanTableUtil.fromCSV(this, csvFile, separator);
	}
	
	/**
	 * @param csvLines
	 * @param separator
	 * @throws IOException
	 */
	public final void fromCSV(final List<String> csvLines, final CSVSeparator separator) throws IOException {
	
		BeanTableUtil.fromCSV(this, csvLines, separator);
	}
	
	/**
	 * @param csvString
	 * @param separator
	 * @throws IOException
	 */
	public final void fromCSV(final String csvString, final CSVSeparator separator) throws IOException {
	
		fromCSV(Arrays.asList(csvString.split(SystemProperties.getLineSeparator())), separator);
	}
	
	/**
	 * @param jsonFile
	 * @throws IOException
	 */
	@SuppressWarnings("unchecked")
	public final void fromJSON(final File jsonFile) throws IOException {
	
		final Type type = new TypeToken<Collection<Bean>>() {}.getType();
		
		setRows((Collection<Bean>)new Gson().fromJson(new FileReader(jsonFile), type));
	}
	
	/**
	 * @param xmlFile
	 * @throws IOException
	 */
	@SuppressWarnings("unchecked")
	public final void fromXML(final File xmlFile) throws IOException {
	
		setRows((Collection<Bean>)new XStream(new DomDriver()).fromXML(xmlFile));
	}
	
	/**
	 * @param xmlStream
	 * @throws IOException
	 */
	@SuppressWarnings("unchecked")
	public final void fromXML(final InputStream xmlStream) throws IOException {
	
		setRows((Collection<Bean>)new XStream(new DomDriver()).fromXML(xmlStream));
	}
	
	/**
	 * @param xmlReader
	 * @throws IOException
	 */
	@SuppressWarnings("unchecked")
	public final void fromXML(final Reader xmlReader) throws IOException {
	
		setRows((Collection<Bean>)new XStream(new DomDriver()).fromXML(xmlReader));
	}
	
	/**
	 * @param xmlString
	 * @throws IOException
	 */
	@SuppressWarnings("unchecked")
	public final void fromXML(final String xmlString) throws IOException {
	
		setRows((Collection<Bean>)new XStream(new DomDriver()).fromXML(xmlString));
	}
	
	/**
	 * @param xmlUrl
	 * @throws IOException
	 */
	@SuppressWarnings("unchecked")
	public final void fromXML(final URL xmlUrl) throws IOException {
	
		setRows((Collection<Bean>)new XStream(new DomDriver()).fromXML(xmlUrl));
	}
	
	/**
	 * @param rowIndex
	 * @return bean
	 */
	public final Bean getBean(final int rowIndex) {
	
		return getRow(rowIndex);
	}
	
	/**
	 * @return the beanClass
	 */
	public final Class<?> getBeanClass() {
	
		return beanClass;
	}
	
	/**
	 * @param columnIndex
	 * @return column or null
	 */
	public final BeanTableColumn getColumn(final int columnIndex) {
	
		return getModel().getColumn(columnIndex);
	}
	
	/**
	 * @param columnIndex
	 * @return alias
	 */
	public final String getColumnAlias(final int columnIndex) {
	
		return getModel().getColumnAlias(columnIndex);
	}
	
	/**
	 * @param columnName
	 * @return alias
	 */
	public final String getColumnAlias(final String columnName) {
	
		return getModel().getColumnAlias(columnName);
	}
	
	/**
	 * @param columnAlias
	 * @return column or null
	 */
	public final BeanTableColumn getColumnByAlias(final String columnAlias) {
	
		return getModel().getColumnByAlias(columnAlias);
	}
	
	/**
	 * @param columnName
	 * @return column or null
	 */
	public final BeanTableColumn getColumnByName(final String columnName) {
	
		return getModel().getColumnByName(columnName);
	}
	
	/**
	 * @param columnName
	 * @return index
	 */
	public final int getColumnIndex(final String columnName) {
	
		return getModel().getColumnIndex(columnName);
	}
	
	/**
	 * @param columnIndex
	 * @return the Max Width
	 */
	public final int getColumnMaxWidth(final int columnIndex) {
	
		return getColumn(getColumnName(columnIndex)).getMaxWidth();
	}
	
	/**
	 * @param columnIndex
	 * @return the Min Width
	 */
	public final int getColumnMinWidth(final int columnIndex) {
	
		return getColumn(getColumnName(columnIndex)).getMinWidth();
	}
	
	/**
	 * @return the columnModel
	 */
	@Override
	public final DefaultTableColumnModel getColumnModel() {
	
		if(columnModel == null) {
			
			columnModel = (DefaultTableColumnModel)super.getColumnModel();
		}
		
		return columnModel;
	}
	
	/**
	 * @param columnAlias
	 * @return a string containing the name of <code>column</code>
	 */
	public final String getColumnName(final String columnAlias) {
	
		return getModel().getColumnName(columnAlias);
	}
	
	/**
	 * @param columnIndex
	 * @return the Preferred Width
	 */
	public final int getColumnPreferredWidth(final int columnIndex) {
	
		return getColumn(getColumnName(columnIndex)).getPreferredWidth();
	}
	
	/**
	 * @return the columns
	 */
	public final ListSet<BeanTableColumn> getColumns() {
	
		return getModel().getColumns();
	}
	
	/**
	 * @return the columns classes
	 */
	public final List<Class<?>> getColumnsClasses() {
	
		return getModel().getColumnsClasses();
	}
	
	/**
	 * @param columnIndex
	 * @return column values
	 */
	public final List<Object> getColumnValues(final int columnIndex) {
	
		return getModel().getColumnValues(columnIndex);
	}
	
	/**
	 * @param columnAlias
	 * @return column values
	 */
	public final List<Object> getColumnValuesByAlias(final String columnAlias) {
	
		return getModel().getColumnValuesByAlias(columnAlias);
	}
	
	/**
	 * @param columnName
	 * @return column values
	 */
	public final List<Object> getColumnValuesByName(final String columnName) {
	
		return getModel().getColumnValuesByName(columnName);
	}
	
	/**
	 * @param columnIndex
	 * @return the Width
	 */
	public final int getColumnWidth(final int columnIndex) {
	
		return getColumn(getColumnName(columnIndex)).getWidth();
	}
	
	/**
	 * @return the First Bean
	 */
	public final Bean getFirstBean() {
	
		return getModel().getFirstRow();
	}
	
	/**
	 * @return the First Bean
	 */
	public final Bean getFirstRow() {
	
		return getModel().getFirstRow();
	}
	
	@Override
	public final int getIndex() {
	
		return index;
	}
	
	/**
	 * @return the Last Bean
	 */
	public final Bean getLastBean() {
	
		return getModel().getLastRow();
	}
	
	/**
	 * @return the Last Bean
	 */
	public final Bean getLastRow() {
	
		return getModel().getLastRow();
	}
	
	/**
	 * @return last row index
	 */
	public final int getLastRowIndex() {
	
		return getModel().getLastRowIndex();
	}
	
	/**
	 * @return the lastSelectedRow
	 */
	public final int getLastSelectedRow() {
	
		return lastSelectedRow;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.JTable#getModel()
	 */
	@Override
	@SuppressWarnings("unchecked")
	public final BeanTableModel<Bean> getModel() {
	
		return (BeanTableModel<Bean>)super.getModel();
	}
	
	/**
	 * @param table
	 * @return {@link Image} or <b>null</b>
	 */
	public final Image getOwnerWindowIcon() {
	
		return getOwnerWindowIcon(getParent());
	}
	
	/**
	 * @param rowIndex
	 * @return bean
	 */
	public final Bean getRow(final int rowIndex) {
	
		return getModel().getRow(convertRowIndexToModel(rowIndex));
	}
	
	/**
	 * @return the rows
	 */
	public final List<Bean> getRows() {
	
		return getModel().getRows();
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.JTable#getRowSorter()
	 */
	@SuppressWarnings("unchecked")
	@Override
	public final TableRowSorter<? extends BeanTableModel<Bean>> getRowSorter() {
	
		try {
			//
			return (TableRowSorter<? extends BeanTableModel<Bean>>)super.getRowSorter();
			
		}catch(final NullPointerException nullPointerException) {
			
			return null;
		}
	}
	
	/**
	 * @return selected bean
	 */
	public final Bean getSelectedBean() {
	
		return getRow(super.getSelectedRow());
	}
	
	/**
	 * @return selected beans
	 */
	public final List<Bean> getSelectedBeans() {
	
		final List<Bean> selected = new ArrayList<>();
		
		//
		for(final int rowIndex : super.getSelectedRows()) {
			
			selected.add(getBean(rowIndex));
		}
		
		return selected;
	}
	
	/**
	 * @param row
	 * @param columnName
	 * @return the Object at the specified cell
	 */
	public final Object getValueAt(final int row, final String columnName) {
	
		return getValueAt(row, getColumnIndex(columnName));
	}
	
	/**
	 * @return the number of visible columns in the table
	 */
	public final int getVisibleColumnCount() {
	
		return getVisibleColumns().size();
	}
	
	/**
	 * @return the visible columns
	 */
	public final ListSet<BeanTableColumn> getVisibleColumns() {
	
		final ListSet<BeanTableColumn> visibleColumns = new ListSet<>();
		
		//
		for(final BeanTableColumn column : getColumns()) {
			
			//
			if(column.isVisible()) {
				
				visibleColumns.add(column);
			}
		}
		
		return visibleColumns;
	}
	
	/**
	 * @return the acceptDuplicateRows
	 */
	public final boolean isAcceptDuplicateRows() {
	
		return getModel().isAcceptDuplicateRows();
	}
	
	@Override
	public final boolean isAtributte() {
	
		return atributte;
	}
	
	/**
	 * @param columnIndex
	 * @return column is editable
	 */
	public final boolean isColumnEditable(final int columnIndex) {
	
		return getModel().isColumnEditable(columnIndex);
	}
	
	/**
	 * @return true if {@code getRowCount() == 0}
	 */
	public final boolean isEmpty() {
	
		return getRowCount() == 0;
	}
	
	/**
	 * @return the locked
	 */
	public final boolean isLocked() {
	
		return getModel().isLocked();
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxSwing.component.JXComponent#isRequired()
	 */
	@Override
	public final boolean isRequired() {
	
		return required;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxSwing.component.JXComponent#isRequiredBorderEnabled()
	 */
	@Override
	public final boolean isRequiredBorderEnabled() {
	
		return requiredBorderEnabled;
	}
	
	/**
	 * @param rowIndex
	 * @return row is editable
	 */
	public final boolean isRowEditable(final int rowIndex) {
	
		return getModel().isRowEditable(rowIndex);
	}
	
	/**
	 *
	 */
	public final synchronized void justifyColumnsSize() {
	
		//
		if(getAutoResizeMode() == AUTO_RESIZE_OFF) {
			
			final Cursor defaultCursor = getCursor();
			
			setCursor(new Cursor(Cursor.WAIT_CURSOR));
			
			//
			for(int columnIndex = 0; columnIndex < getColumnCount(); columnIndex++) {
				
				packColumnToData(columnIndex);
			}
			
			setCursor(defaultCursor);
		}
	}
	
	/**
	 *
	 */
	public final void locked() {
	
		getModel().locked();
	}
	
	/**
	 * @param tableModelEvent
	 */
	public final void newDataAvailable(final TableModelEvent tableModelEvent) {
	
		getModel().newDataAvailable(tableModelEvent);
	}
	
	/**
	 * @param tableModelEvent
	 */
	public final void newRowsAdded(final TableModelEvent tableModelEvent) {
	
		getModel().newRowsAdded(tableModelEvent);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.JTable#print()
	 */
	@Override
	public final boolean print() throws PrinterException {
	
		preparePrintTable();
		
		final boolean result = super.print();
		
		restorePrintTable();
		
		return result;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.JTable#print(javax.swing.JTable.PrintMode)
	 */
	@Override
	public final boolean print(final PrintMode printMode) throws PrinterException {
	
		preparePrintTable();
		
		final boolean result = super.print(printMode);
		
		restorePrintTable();
		
		return result;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.JTable#print(javax.swing.JTable.PrintMode, java.text.MessageFormat, java.text.MessageFormat)
	 */
	@Override
	public final boolean print(final PrintMode printMode, final MessageFormat headerFormat, final MessageFormat footerFormat) throws PrinterException {
	
		preparePrintTable();
		
		final boolean result = super.print(printMode, headerFormat, footerFormat);
		
		restorePrintTable();
		
		return result;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.JTable#print(javax.swing.JTable.PrintMode, java.text.MessageFormat, java.text.MessageFormat, boolean, javax.print.attribute.PrintRequestAttributeSet, boolean)
	 */
	@Override
	public final boolean print(final PrintMode printMode, final MessageFormat headerFormat, final MessageFormat footerFormat, final boolean showPrintDialog, final PrintRequestAttributeSet attr, final boolean interactive) throws PrinterException, HeadlessException {
	
		preparePrintTable();
		
		final boolean result = super.print(printMode, headerFormat, footerFormat, showPrintDialog, attr, interactive);
		
		restorePrintTable();
		
		return result;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.JTable#print(javax.swing.JTable.PrintMode, java.text.MessageFormat, java.text.MessageFormat, boolean, javax.print.attribute.PrintRequestAttributeSet, boolean,
	 * javax.print.PrintService)
	 */
	@Override
	public final boolean print(final PrintMode printMode, final MessageFormat headerFormat, final MessageFormat footerFormat, final boolean showPrintDialog, final PrintRequestAttributeSet attr, final boolean interactive, final PrintService service) throws PrinterException, HeadlessException {
	
		preparePrintTable();
		
		final boolean result = super.print(printMode, headerFormat, footerFormat, showPrintDialog, attr, interactive, service);
		
		restorePrintTable();
		
		return result;
	}
	
	/**
	 * @param bean
	 */
	public final void removeRow(final Bean bean) {
	
		getModel().removeRow(bean);
		
		configNewRow();
	}
	
	/**
	 * @param rowIndex
	 */
	public final void removeRow(final int rowIndex) {
	
		getModel().removeRow(rowIndex);
		
		configNewRow();
	}
	
	/**
	 *
	 */
	public final void removeSelectedRows() {
	
		final List<Bean> selectedBeans = getSelectedBeans();
		
		//
		for(final Bean bean : selectedBeans) {
			
			removeRow(bean);
		}
	}
	
	/**
	 * @param tableModelEvent
	 */
	public final void rowsRemoved(final TableModelEvent tableModelEvent) {
	
		getModel().rowsRemoved(tableModelEvent);
	}
	
	/**
	 * @param rowIndex
	 * @param columnIndex
	 * @see http://www.java2s.com/Tutorial/Java/0240__Swing/MakingaCellVisibleinaJTableComponent.htm
	 */
	public final void scrollToVisible(final int rowIndex, final int columnIndex) {
	
		//
		if( !(getParent() instanceof JViewport)) {
			
			return;
		}
		
		//
		final JViewport viewport = (JViewport)getParent();
		
		final Rectangle rectangle = getCellRect(rowIndex, columnIndex, true);
		
		final Point point = viewport.getViewPosition();
		
		rectangle.setLocation(rectangle.x - point.x, rectangle.y - point.y);
		
		viewport.scrollRectToVisible(rectangle);
	}
	
	/**
	 * @param acceptDuplicateRows
	 *        the acceptDuplicateRows to set
	 */
	public final void setAcceptDuplicateRows(final boolean acceptDuplicateRows) {
	
		getModel().setAcceptDuplicateRows(acceptDuplicateRows);
	}
	
	@Override
	public final void setAtributte(final boolean atributte) {
	
		this.atributte = atributte;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.JTable#setAutoResizeMode(int)
	 */
	@Override
	public final void setAutoResizeMode(final int mode) {
	
		super.setAutoResizeMode(mode);
		
		justifyColumnsSize();
	}
	
	/**
	 * @param columnIndex
	 * @param columnAlias
	 */
	public final void setColumnAlias(final int columnIndex, final String columnAlias) {
	
		final BeanTableColumn beanTableColumn = getModel().getColumn(columnIndex);
		final TableColumn tableColumn = getColumn(beanTableColumn.getAlias());
		
		tableColumn.setIdentifier(columnAlias);
		beanTableColumn.setAlias(columnAlias);
		
		getModel().fireTableStructureChanged();
	}
	
	/**
	 * @param columnName
	 * @param columnAlias
	 */
	public final void setColumnAlias(final String columnName, final String columnAlias) {
	
		final BeanTableColumn beanTableColumn = getModel().getColumnByName(columnName);
		final TableColumn tableColumn = getColumn(beanTableColumn.getAlias());
		
		tableColumn.setIdentifier(columnAlias);
		beanTableColumn.setAlias(columnAlias);
		
		getModel().fireTableStructureChanged();
	}
	
	/**
	 * @param columnIndex
	 * @param editable
	 */
	public final void setColumnEditable(final int columnIndex, final boolean editable) {
	
		getModel().setColumnEditable(columnIndex, editable);
	}
	
	/**
	 * @param columnIndex
	 * @param maxWidth
	 */
	public final void setColumnMaxWidth(final int columnIndex, final int maxWidth) {
	
		getColumn(getColumnName(columnIndex)).setMaxWidth(maxWidth);
	}
	
	/**
	 * @param columnIndex
	 * @param minWidth
	 */
	public final void setColumnMinWidth(final int columnIndex, final int minWidth) {
	
		getColumn(getColumnName(columnIndex)).setMinWidth(minWidth);
	}
	
	/**
	 * @param columnIndex
	 * @param preferredWidth
	 */
	public final void setColumnPreferredWidth(final int columnIndex, final int preferredWidth) {
	
		getColumn(getColumnName(columnIndex)).setPreferredWidth(preferredWidth);
	}
	
	/**
	 * @param visible
	 * @param columnIndexes
	 */
	public final void setColumnVisible(final boolean visible, final int... columnIndexes) {
	
		//
		if(CheckUtil.isNotNullAndNotEmpty(columnIndexes)) {
			
			//
			for(final int columnIndex : columnIndexes) {
				
				setColumnVisible(columnIndex, visible);
			}
		}
	}
	
	/**
	 * @param visible
	 * @param columnNames
	 */
	public final void setColumnVisible(final boolean visible, final String... columnNames) {
	
		//
		if(CheckUtil.isNotNullAndNotEmpty(columnNames)) {
			
			//
			for(final String columnName : columnNames) {
				
				setColumnVisible(columnName, visible);
			}
		}
	}
	
	/**
	 * @param columnIndex
	 * @param visible
	 */
	public final void setColumnVisible(final int columnIndex, final boolean visible) {
	
		final TableColumn tableColumn = getColumnModel().getColumn(columnIndex);
		
		tableColumn.setMinWidth(0);
		tableColumn.setMaxWidth(0);
		tableColumn.setResizable(false);
	}
	
	/**
	 * @param columnName
	 * @param visible
	 */
	public final void setColumnVisible(final String columnName, final boolean visible) {
	
		setColumnVisible(getColumnIndex(columnName), visible);
	}
	
	/**
	 * @param visible
	 * @param columnAlias
	 */
	public final void setColumnVisibleByAlias(final boolean visible, final String... columnAlias) {
	
		//
		if(CheckUtil.isNotNullAndNotEmpty(columnAlias)) {
			
			//
			for(final String colAlias : columnAlias) {
				
				setColumnVisible(getColumnName(colAlias), visible);
			}
		}
	}
	
	/**
	 * @param columnAlias
	 * @param visible
	 */
	public final void setColumnVisibleByAlias(final String columnAlias, final boolean visible) {
	
		setColumnVisible(getColumnName(columnAlias), visible);
	}
	
	/**
	 * @param columnIndex
	 * @param width
	 */
	public final void setColumnWidth(final int columnIndex, final int width) {
	
		getColumn(getColumnName(columnIndex)).setWidth(width);
	}
	
	@Override
	public final void setIndex(final int index) {
	
		this.index = index;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see java.awt.Component#setName(java.lang.String)
	 */
	@Override
	public final void setName(final String name) {
	
		super.setName(name);
		
		setRequired(isRequired());
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxSwing.component.JXComponent#setRequired(boolean)
	 */
	@Override
	public final void setRequired(final boolean required) {
	
		this.required = required;
		
		//
		if(required) {
			
			//
			if(isRequiredBorderEnabled()) {
				
				setBorder(new LineBorder(Color.RED));
			}
			
			//
			if(CheckUtil.isNullOrAbsoluteEmpty(getName())) {
				
				setToolTipText(I18N.component().requiredThis());
				
			}else {
				
				setToolTipText(I18N.component().requiredField(getName()));
			}
			
		}else {
			
			setBorder(UIManager.getBorder("Table.scrollPaneBorder"));
			setToolTipText(null);
		}
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jxSwing.component.JXComponent#setRequiredBorderEnabled(boolean)
	 */
	@Override
	public final void setRequiredBorderEnabled(final boolean requiredBorderEnabled) {
	
		this.requiredBorderEnabled = requiredBorderEnabled;
	}
	
	/**
	 * @param rowIndex
	 * @param editable
	 */
	public final void setRowEditable(final int rowIndex, final boolean editable) {
	
		getModel().setRowEditable(rowIndex, editable);
	}
	
	/**
	 * @param rows
	 *        the rows to set
	 */
	public final void setRows(final Bean[] beans) {
	
		setRows(Arrays.asList(beans));
	}
	
	/**
	 * @param rows
	 *        the rows to set
	 */
	public final void setRows(final Collection<Bean> beans) {
	
		getModel().setRows(beans);
		
		scrollToVisible(0, 0);
	}
	
	/**
	 * @param columnIndex
	 */
	public final void setSelectedColumn(final int columnIndex) {
	
		addColumnSelectionInterval(columnIndex, columnIndex);
	}
	
	/**
	 * @param columnIndexes
	 */
	public final void setSelectedColumns(final int... columnIndexes) {
	
		ParameterCheck.isNullOrEmpty(columnIndexes, "columnIndexes");
		
		//
		for(final int columnIndex : columnIndexes) {
			
			setSelectedColumn(columnIndex);
		}
	}
	
	/**
	 * @param rowIndex
	 */
	public final void setSelectedRow(final int rowIndex) {
	
		addRowSelectionInterval(rowIndex, rowIndex);
	}
	
	/**
	 * @param rowIndexes
	 */
	public final void setSelectedRows(final int... rowIndexes) {
	
		ParameterCheck.isNullOrEmpty(rowIndexes, "rowIndexes");
		
		//
		for(final int rowIndex : rowIndexes) {
			
			setSelectedRow(rowIndex);
		}
	}
	
	/**
	 * @param sizeToDialog
	 */
	public final void setSizeToDialogInBeanEditor(final Dimension sizeToDialog) {
	
		//
		for(final BeanTableColumn beanTableColumn : getColumns()) {
			
			final TableColumn tableColumn = getColumn(beanTableColumn.getAlias());
			
			//
			if(tableColumn.getCellEditor() != null) {
				
				//
				if(tableColumn.getCellEditor() instanceof BeanEditor) {
					
					((BeanEditor)tableColumn.getCellEditor()).setSizeToDialogInBeanEditor(sizeToDialog);
				}
			}
		}
	}
	
	/**
	 * @param showGrid
	 */
	public final void showGrid(final boolean showGrid) {
	
		setShowGrid(showGrid);
	}
	
	/**
	 * @return the showLineNumber
	 */
	public final boolean showLineNumber() {
	
		return showLineNumber;
	}
	
	/**
	 * @param showLineNumber
	 */
	public final void showLineNumber(final boolean showLineNumber) {
	
		this.showLineNumber = showLineNumber;
	}
	
	/**
	 *
	 */
	public final void showOnlyHorizontalGridLines() {
	
		showGrid(false);
		setShowHorizontalLines(true);
	}
	
	/**
	 *
	 */
	public final void showOnlyVerticalGridLines() {
	
		showGrid(false);
		setShowVerticalLines(true);
	}
	
	/**
	 * @param columnIndex
	 */
	public final void sorter(final int columnIndex) {
	
		//
		if(getRowSorter() != null) {
			
			getRowSorter().toggleSortOrder(columnIndex);
		}
	}
	
	/**
	 * @param columnAlias
	 */
	public final void sorter(final String columnAlias) {
	
		sorter(getColumnIndex(getColumnName(columnAlias)));
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.JTable#sorterChanged(javax.swing.event.RowSorterEvent)
	 */
	@Override
	public final void sorterChanged(final RowSorterEvent rowSorterEvent) {
	
		final List<?> sortKeys = rowSorterEvent.getSource().getSortKeys();
		
		//
		if( !sortKeys.isEmpty()) {
			
			final SortKey sortKey = (SortKey)sortKeys.get(0);
			
			final BeanComparator comparator = new BeanComparator(sortKey.getColumn(), sortKey.getSortOrder());
			
			Collections.sort(getRows(), comparator);
		}
		
		//
		super.sorterChanged(rowSorterEvent);
	}
	
	/**
	 * exportInvisibleColumns = false
	 * 
	 * @return csv String
	 * @throws IOException
	 */
	public final String toCSV() throws IOException {
	
		return toCSV(false);
	}
	
	/**
	 * @param exportInvisibleColumns
	 * @return csv String
	 * @throws IOException
	 */
	public final String toCSV(final boolean exportInvisibleColumns) throws IOException {
	
		return toCSV(CSVSeparator.SEMI_COLON);
	}
	
	/**
	 * exportInvisibleColumns = false
	 * 
	 * @param separator
	 * @return csv String
	 * @throws IOException
	 */
	public final String toCSV(final CSVSeparator separator) throws IOException {
	
		return toCSV(separator, false);
	}
	
	/**
	 * @param separator
	 * @param exportInvisibleColumns
	 * @return csv String
	 * @throws IOException
	 */
	public final String toCSV(final CSVSeparator separator, final boolean exportInvisibleColumns) throws IOException {
	
		return BeanTableUtil.toCSV(this, separator, exportInvisibleColumns);
	}
	
	/**
	 * exportInvisibleColumns = false
	 * 
	 * @param csvFile
	 * @param separator
	 * @throws IOException
	 */
	public final void toCSV(final File csvFile, final CSVSeparator separator) throws IOException {
	
		toCSV(csvFile, separator, false);
	}
	
	/**
	 * @param csvFile
	 * @param separator
	 * @param exportInvisibleColumns
	 * @throws IOException
	 */
	public final void toCSV(final File csvFile, final CSVSeparator separator, final boolean exportInvisibleColumns) throws IOException {
	
		new JXFile(csvFile).writeString(toCSV(separator, exportInvisibleColumns));
	}
	
	/**
	 * @param gifFile
	 * @throws IOException
	 */
	public final void toGIF(final File gifFile) throws IOException {
	
		BeanTableUtil.toGIF(this, gifFile);
	}
	
	/**
	 * exportInvisibleColumns = false
	 * 
	 * @param htmlFile
	 * @throws IOException
	 * @throws ImageFormatException
	 */
	public final void toHTML(final File htmlFile) throws IOException, ImageFormatException {
	
		toHTML(htmlFile, false);
	}
	
	/**
	 * @param htmlFile
	 * @param exportInvisibleColumns
	 * @throws IOException
	 * @throws ImageFormatException
	 */
	public final void toHTML(final File htmlFile, final boolean exportInvisibleColumns) throws IOException, ImageFormatException {
	
		BeanTableUtil.toHTML(this, htmlFile, exportInvisibleColumns);
	}
	
	/**
	 * @return bufferedImage
	 * @throws IOException
	 */
	public final BufferedImage toImage() throws IOException {
	
		return BeanTableUtil.toImage(this);
	}
	
	/**
	 * @param jpgFile
	 * @throws IOException
	 */
	public final void toJPG(final File jpgFile) throws IOException {
	
		BeanTableUtil.toJPG(this, jpgFile);
	}
	
	/**
	 * @return json String
	 */
	public final String toJSON() {
	
		return new Gson().toJson(getRows());
	}
	
	/**
	 * @param jsonFile
	 * @throws IOException
	 */
	public final void toJSON(final File jsonFile) throws IOException {
	
		new JXFile(jsonFile).writeString(toJSON());
	}
	
	/**
	 * @param pngFile
	 * @throws IOException
	 */
	public final void toPNG(final File pngFile) throws IOException {
	
		BeanTableUtil.toPNG(this, pngFile);
	}
	
	/**
	 * exportInvisibleColumns = false
	 * 
	 * @param tableName
	 * @param xlsFile
	 * @throws Throwable
	 */
	public final void toXLS(final String tableName, final File xlsFile) throws Throwable {
	
		toXLS(tableName, xlsFile, false);
	}
	
	/**
	 * @param tableName
	 * @param xlsFile
	 * @param exportInvisibleColumns
	 * @throws Throwable
	 */
	public final void toXLS(final String tableName, final File xlsFile, final boolean exportInvisibleColumns) throws Throwable {
	
		BeanTableUtil.toXLS(this, xlsFile, CheckUtil.isNullOrAbsoluteEmpty(tableName) ? getBeanClass().getSimpleName() : tableName.trim(), exportInvisibleColumns);
	}
	
	/**
	 * @return xml String
	 */
	public final String toXML() {
	
		return new XStream(new DomDriver()).toXML(getRows());
	}
	
	/**
	 * @param xmlFile
	 * @throws IOException
	 */
	public final void toXML(final File xmlFile) throws IOException {
	
		new JXFile(xmlFile).writeString(toXML());
	}
	
	/**
	 *
	 */
	public final void unLocked() {
	
		getModel().unLocked();
	}
	
	/**
	 * @author Denilson Edinaldo Pinto
	 * @porject jxSwing
	 * @package org.jxSwing.component.table
	 * @date 21/03/2014 - 22:24:29
	 */
	public final class BeanComparator implements Comparator<Bean> {
		
		public int callCount = 0;
		
		private int columnIndex = 0;
		
		private SortOrder order = SortOrder.ASCENDING;
		
		/**
		 * @param columnIndex
		 * @param order
		 */
		public BeanComparator(final int columnIndex, final SortOrder order) {
		
			this.columnIndex = columnIndex;
			this.order = order;
		}
		
		@Override
		public int compare(final Bean bean1, final Bean bean2) {
		
			callCount++;
			
			try {
				//
				final Object valueBean1 = getColumn(columnIndex).getBeanMethodGet().invoke(bean1);
				final Object valueBean2 = getColumn(columnIndex).getBeanMethodGet().invoke(bean2);
				
				final Comparator<Object> comparator = CollectionUtil.makePrimitiveComparator();
				
				//
				if(order == SortOrder.ASCENDING) {
					
					return comparator.compare(valueBean1, valueBean2);
					
				}else if(order == SortOrder.DESCENDING) {
					
					return comparator.compare(valueBean2, valueBean1);
				}
				
			}catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException exception) {
				
				exception.printStackTrace();
			}
			
			return 0;
		}
	}
}
